//
// ServerConnection.cs
//
// Copyright (c) 2007 Lukas Lipka.
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Reflection;

using IcqSharp.Packets;
using IcqSharp.Util;

namespace IcqSharp.Connections
{
    public class ServerConnection : Connection
    {
        private static Hashtable handlers = null;

        private Timer keepalive_timer;
        private byte[] cookie;

        static ServerConnection()
        {
            handlers = new Hashtable();

            foreach (Type type in ReflectionFu.ScanAssemblyForTypes(typeof(PacketHandler)))
            {
                SnacPacketType pt = (SnacPacketType)ReflectionFu.ScanTypeForAttribute(type, typeof(SnacPacketType));
                handlers[GetHash(pt.Family, pt.Command)] = type;
            }
        }

        public ServerConnection(Session session, string host, int port, byte[] cookie)
            : base(session, host, port)
        {
            this.cookie = cookie;

            // Setup our keepalive timer to send packets
            keepalive_timer = new Timer(120000);
            keepalive_timer.Timeout += OnKeepAliveTimeout;
            keepalive_timer.Loop = true;

            session.Connected += OnSessionConnected;
            session.Disconnected += OnSessionDisconnected;
        }

        public new void Connect()
        {
            base.Connect();
        }

        protected void OnSessionConnected(object o, EventArgs args)
        {
            Log.Debug("Starting keepalive timer");
            keepalive_timer.Start();
        }

        protected void OnSessionDisconnected(object o, EventArgs args)
        {
            Log.Debug("Stopping keepalive timer");
            keepalive_timer.Stop();
        }

        private void OnKeepAliveTimeout(object o, EventArgs args)
        {
            Log.Debug("Sending keepalive packet!");

            FlapPacket p = new FlapPacket(0x05);
            Send(p);
        }

        public void Send(FlapPacket packet)
        {
            Log.Debug("Sending packet: '{0}'", packet.GetType());

            // Get the sequence number for this packet
            ushort sequence = Session.GetSequenceNumber();
            packet.SetSequence(sequence);

            byte[] data = packet.Write();
            Write(data);
        }

        private void SendCookie()
        {
            // Send cookie
            FlapPacket p = new FlapPacket(0x01);
            p.AddDword(0x00000001);
            p.AddTlv(new Tlv(0x06, cookie));

            Send(p);
        }

        private static int GetHash(ushort family, ushort command)
        {
            return ((family) | (command << 16));
        }

        protected override void HandleData(byte[] data)
        {
            if (data[0] != FlapPacket.Id)
                throw new FormatException("Invalid FlapPacket ID");

            Channels channel = (Channels)data[1];

            Log.Debug("Received packet (Channel: {0:X})", channel);

            switch (channel)
            {
                case Channels.NewConnection:
                    SendCookie();
                    break;

                case Channels.SnacData:
                    SnacPacket snac_packet = SnacPacket.Parse(data);
                    HandlePacket(snac_packet);
                    break;

                case Channels.FlapError:
                    FlapPacket error_packet = FlapPacket.Parse(data);
                    HandleError(error_packet);
                    break;

                case Channels.CloseConnection:
                    FlapPacket close_packet = FlapPacket.Parse(data);
                    HandleCloseConnection(close_packet);
                    Session.OnConnectionError(null, EventArgs.Empty);
                    Session.OnDisconnected(null, EventArgs.Empty);
                    break;

                default:
                    Log.Debug("Received packet on unhandled channel '{0:X}' ", channel);
                    break;
            }
        }

        private void HandlePacket(SnacPacket packet)
        {
            Log.Debug("SnacPacket (Family: {0:X} Command: {1:X})", packet.Family, packet.Command);

            // If the command is 0x0001, then this is most
            // likely an error so handle this
            if (packet.Command == 0x0001)
            {
                ushort error_code = LittleEndianBitConverter.Big.ToUInt16(packet.Data, 0);
                string error = ErrorCodes.GetError(error_code);

                Log.Error("Snac({0:X},{1:X}) - {2}", packet.Family, packet.Command, error);
            }

            int hash = GetHash(packet.Family, packet.Command);

            if (handlers.ContainsKey(hash))
            {
                Type type = (Type)handlers[hash];

                Log.Debug("Creating packet handler - '{0}'", type);

                PacketHandler h = null;

                try
                {
                    h = (PacketHandler)ReflectionFu.CreateInstance(type, Session, packet);
                }
                catch (Exception e)
                {
                    Log.Error("Exception while instantiating '{0}':", type);
                    Log.Error(e);
                    return;
                }

                List<FlapPacket> response_list = h.Response(packet);

                if (response_list != null)
                {
                    foreach (FlapPacket response in response_list)
                    {
                        Send(response);
                        System.Threading.Thread.Sleep(100);
                    }
                }

                return;
            }

            Log.Warn("No handler for SnacPacket (Family: {0:X} Command: {1:X})", packet.Family, packet.Command);
        }

        private void HandleError(FlapPacket packet)
        {
            Log.Error("Received error packet! (FIXME: Implement error handling)");
        }

        private void HandleCloseConnection(FlapPacket packet)
        {
            Log.Error("Disconnected by server!");

            TlvList tlvs = TlvList.Parse(packet.Data);

            if (tlvs[0x0009] != null)
            {
                Log.Error(Errors.GetError(tlvs[0x0009].ToUInt16()));
            }
        }
    }
}

